
/*
 *  
 *
 *  Copyright (C) Microsoft Corporation.  All rights reserved
 *
 *  
 */


using System;
using System.Collections.Generic;
using System.Text;
using Word = Microsoft.Office.Interop.Word;
using Core = Microsoft.Office.Core;
using System.Windows.Forms;
using Microsoft.VisualStudio.Tools.Applications.Runtime;


namespace CRMScenarios
{
    /// <summary>
    /// Helper routines for handling word document.
    /// </summary>
    static class WordHelper
    {

        static Word.Application application = null;
        static Word.ApplicationClass appClass = null;
        static Core.CommandBar toolBar = null;
        static Core.CommandBarButton insFieldBtn = null;
        static Core.CommandBarButton attachDocBtn = null;
        static Dictionary<int, ActionsPane> MappingFromDocToForm = new Dictionary<int, ActionsPane>();

        /// <summary>
        /// Get the toolbars, create if not available.
        /// Get the buttons, Create if not availabe.
        /// Have the even handlers ready.
        /// </summary>
        /// <param name="app"></param>
        /// <returns></returns>
        public static int Initialization(Word.Application app)
        {
            object missing = System.Reflection.Missing.Value;

            Helper.Trace("public static int Initialization(Word.Application app)", true);

            if (app != null)
            {
                application = app;
                appClass = (Word.ApplicationClass)app;
            }

            try
            {
                toolBar = (Core.CommandBar)application.CommandBars[global::CRMScenarios.Resources.ToolBarName];
            }
            catch (Exception noTB)
            {
                Helper.Trace("Toolbar not present already\n" + noTB.StackTrace, false);
                try
                {
                    toolBar = (Core.CommandBar)application.CommandBars.Add(global::CRMScenarios.Resources.ToolBarName, 1, missing, true);
                }
                catch (Exception tBCreate)
                {
                    Helper.Trace("Toolbar creation failed\n" + tBCreate.StackTrace, false );
                    MessageBox.Show(global::CRMScenarios.Resources.ToolBarCreateFailureMsg, global::CRMScenarios.Resources.DialogBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return -1;
                }
            }


            try
            {
                insFieldBtn = (Core.CommandBarButton)toolBar.Controls["B3304881-6F3C-44ed-AC08-A8E3BD13606A"];
                attachDocBtn = (Core.CommandBarButton)toolBar.Controls["7C436102-E2FF-488d-B027-8F1A7577F1B6"];
            }
            catch (Exception noBtns)
            {
                Helper.Trace("Buttons not present\n" + noBtns.StackTrace, false);
                try
                {
                    insFieldBtn = (Core.CommandBarButton)toolBar.Controls.Add(1, missing, missing, missing, missing);
                    attachDocBtn = (Core.CommandBarButton)toolBar.Controls.Add(1, missing, missing, missing, missing);
                }

                catch (Exception btnCreate )
                {
                    Helper.Trace("Buttons Create failed\n" + btnCreate.StackTrace, false);
                    //Helper.Trace("Some security Problem: " + innerExp.Message + " \n" + outerExp.Message);
                    MessageBox.Show(global::CRMScenarios.Resources.DialogBoxTitle, global::CRMScenarios.Resources.DialogBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return -1;
                }
            }


            if ((toolBar != null))
            {
                Helper.Trace("ToolBar not null\n", false);
                toolBar.Protection = Microsoft.Office.Core.MsoBarProtection.msoBarNoProtection;
                if (insFieldBtn != null)
                {
                    Helper.Trace("InsBtn not null", false);
                    insFieldBtn.Style = Core.MsoButtonStyle.msoButtonIconAndCaption;
                    insFieldBtn.Caption = global::CRMScenarios.Resources.InsertButtonCaption;
		    // Using Guid. Just to make sure that no name collision occurs.	
                    insFieldBtn.Tag = "B3304881-6F3C-44ed-AC08-A8E3BD13606A";
                    insFieldBtn.Click += new Core._CommandBarButtonEvents_ClickEventHandler(insFieldBtn_Click);
                    Clipboard.SetDataObject(global::CRMScenarios.Resources.ico_insertfromcrm1);
                    insFieldBtn.PasteFace();
                    Clipboard.Clear();
                }


                if (attachDocBtn != null)
                {
                    Helper.Trace("AttBtn not null", false);
                    attachDocBtn.Style = Core.MsoButtonStyle.msoButtonIconAndCaption;
                    attachDocBtn.Caption = global::CRMScenarios.Resources.AttachButtonCaption;
                    attachDocBtn.Tag = "7C436102-E2FF-488d-B027-8F1A7577F1B6";
                    attachDocBtn.Click += new Microsoft.Office.Core._CommandBarButtonEvents_ClickEventHandler(attachDocBtn_Click);
                    Clipboard.SetDataObject(global::CRMScenarios.Resources.ico_attachtocrm1);
                    attachDocBtn.PasteFace();
                    Clipboard.Clear();

                }

                toolBar.Enabled = true;
                toolBar.Visible = true;
            }
            else
            {
                Helper.Trace("ToolBar Creation Error", true);
                MessageBox.Show(global::CRMScenarios.Resources.DialogBoxTitle, global::CRMScenarios.Resources.DialogBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            try
            {
                Helper.Trace("Event Handling Begin", false);
                appClass.ApplicationEvents2_Event_NewDocument += new Word.ApplicationEvents2_NewDocumentEventHandler(DocumentOpen);
                appClass.ApplicationEvents2_Event_DocumentOpen += new Word.ApplicationEvents2_DocumentOpenEventHandler(DocumentOpen);
                application.DocumentOpen += new Word.ApplicationEvents4_DocumentOpenEventHandler(DocumentOpen);

                application.DocumentChange += new Word.ApplicationEvents4_DocumentChangeEventHandler(DocumentChange);

                application.DocumentBeforeClose += new Word.ApplicationEvents4_DocumentBeforeCloseEventHandler(DocumentClose);
                appClass.ApplicationEvents2_Event_DocumentBeforeClose += new Microsoft.Office.Interop.Word.ApplicationEvents2_DocumentBeforeCloseEventHandler(DocumentClose);

                application.WindowDeactivate += new Word.ApplicationEvents4_WindowDeactivateEventHandler(application_WindowDeactivate);

                application.WindowActivate += new Word.ApplicationEvents4_WindowActivateEventHandler(application_WindowActivate);

            }
            catch (Exception evtHndling)
            {
                Helper.Trace("Event Handling caught EXP\n" + evtHndling.StackTrace, false);
                MessageBox.Show(global::CRMScenarios.Resources.DialogBoxTitle, global::CRMScenarios.Resources.DialogBoxTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
                cleanUpApplication();
                return -1;
            }
            Helper.Trace("Exiting", false);
            return 0;
        }

        /// <summary>
        /// On actication of the application window, do the necessary.
        /// Bring the form if necessary.
        /// </summary>
        /// <param name="Doc"></param>
        /// <param name="Wn"></param>
        static void application_WindowActivate(Microsoft.Office.Interop.Word.Document Doc, Microsoft.Office.Interop.Word.Window Wn)
        {
            Helper.Trace("static void application_WindowActivate(Microsoft.Office.Interop.Word.Document Doc, Microsoft.Office.Interop.Word.Window Wn)", false);
            if (Doc != null)
            {
                DoActiveOrHideAccordingly(Doc);
            }
        }

        /// <summary>
        /// Appliation window is deactivated. hide things if needed/.
        /// </summary>
        /// <param name="Doc"></param>
        /// <param name="Wn"></param>
        static void application_WindowDeactivate(Microsoft.Office.Interop.Word.Document Doc, Microsoft.Office.Interop.Word.Window Wn)
        {
            Helper.Trace("static void application_WindowDeactivate(Microsoft.Office.Interop.Word.Document Doc, Microsoft.Office.Interop.Word.Window Wn)", false);
            foreach (ActionsPane apToHide in MappingFromDocToForm.Values)
            {
                apToHide.Visible = false;
                apToHide.Hide();
            }
        }

        /// <summary>
        /// Take actions on closing of the document.
        /// </summary>
        /// <param name="Doc"></param>
        /// <param name="Cancel"></param>
        static void DocumentClose(Microsoft.Office.Interop.Word.Document Doc, ref bool Cancel)
        {
            Helper.Trace("static void DocumentClose(Microsoft.Office.Interop.Word.Document Doc, ref bool Cancel)", false);

            if (Doc == null)
            {
                return;
            }

            if (Doc.Application.Documents.Count == 1)
            {
                toolBar.Visible = false;
            }       

            int hash = Doc.GetHashCode();

            if (MappingFromDocToForm.ContainsKey(hash))
            {
                ActionsPane apToDelete = MappingFromDocToForm[hash];
                apToDelete.Dispose();
                MappingFromDocToForm.Remove(hash);
            }
        }

        /// <summary>
        /// Here lies the logic for doing the hide and show of the custom form for outlook mails. 
        /// </summary>
        /// <param name="doc"></param>
        static void DoActiveOrHideAccordingly(Word.Document doc)
        {
            Helper.Trace("static void DoActiveOrHideAccordingly(Word.Document doc)", false);
            try
            {
                if (doc.Email.CurrentEmailAuthor != null)
                {
                    if (attachDocBtn.Enabled == true)
                    {
                        attachDocBtn.Enabled = false;
                    }

                    if (attachDocBtn.Visible == true)
                    {
                        attachDocBtn.Visible = false;
                    }

                    int hash = doc.GetHashCode();
                    if (MappingFromDocToForm.ContainsKey(hash))
                    {
                        ActionsPane apToShow = MappingFromDocToForm[hash];
                        apToShow.Visible = true;
                        apToShow.Show();
                    }
                    return;
                }
            }
            catch (Exception)
            {
                Helper.Trace("Not OUTLOOK", true);
                // this is an expected exception. 
                // dont have to worry. just let it go
            }

            if (attachDocBtn.Enabled == false)
            {
                attachDocBtn.Enabled = true;
            }

            if (attachDocBtn.Visible == false)
            {
                attachDocBtn.Visible = true;
            }

            try
            {
                foreach (ActionsPane apToHide in MappingFromDocToForm.Values)
                {
                    apToHide.Visible = false;
                    apToHide.Hide();
                }
            }
            catch (Exception exp)
            {
                Helper.Trace("Hiding failure\n" + exp.StackTrace, false);
            }
        }

        /// <summary>
        /// The active document has been changed. Get the active one and take action depending on the type 
        /// of it.
        /// </summary>
        static void DocumentChange()
        {
            Helper.Trace("static void DocumentChange()", false);
            Word.Document doc = application.ActiveDocument;
            if (doc == null)
            {
                return;
            }
            DoActiveOrHideAccordingly(doc);            
        }

        /// <summary>
        /// actions to be taken on click of the button. this does 
        /// preparing the document readyt for attaching the assembly and 
        /// makes the attach call
        /// </summary>
        /// <param name="doc"></param>
        /// <param name="scenario"></param>
        private static void ArrangeAndDoAddCustomizationWord(Word.Document doc, int scenario)
        {
            Helper.Trace("private static void ArrangeAndDoAddCustomizationWord(Word.Document doc, int scenario)", false);
            if (Helper.IsTempFile(doc.Path))
            {
                MessageBox.Show(global::CRMScenarios.Resources.TemporaryFileWarning);
                return;
            }

            string installationPath = Helper.GetInstallPath(string.Empty);
            string locationOfassembly = installationPath + @"Assembly\Microsoft.Crm.Office.Word.application";
            closeAttachReOpen(doc, locationOfassembly, scenario);
        }
        
        /// <summary>
        /// actions to be taken on click of the button.
        /// </summary>
        /// <param name="Ctrl"></param>
        /// <param name="CancelDefault"></param>
        static void attachDocBtn_Click(Microsoft.Office.Core.CommandBarButton Ctrl, ref bool CancelDefault)
        {
            Helper.Trace("static void attachDocBtn_Click(Microsoft.Office.Core.CommandBarButton Ctrl, ref bool CancelDefault)", false);
            Word.Document doc = application.ActiveDocument;

            if (doc == null)
            {
                return;
            }

            // Why do we actually look whether the doc is readonly or not? 
            // read only files can have attachdoc with them?? right? 
            if (doc.ReadOnly)
            {
                MessageBox.Show(global::CRMScenarios.Resources.DocReadOnlyMessage);
                return;
            }
            // insertion only is there in outlook.
            try
            {
                if (doc.Email.CurrentEmailAuthor != null)
                {
                    return;
                }
            }
            catch
            {
                Helper.Trace("NOT OTL", false);
            }

            if (OfficeApi.GetCustomProperty(doc, "CRMVSTO") != null)
            {
                // Show actionsPane for CRM.
                application.TaskPanes[Microsoft.Office.Interop.Word.WdTaskPanes.wdTaskPaneDocumentActions].Visible = true;
                return;
            }
            ArrangeAndDoAddCustomizationWord(doc, 2); // 2 is for attach doc
        }

        /// <summary>
        /// actions to be taken on click of the button.
        /// </summary>
        /// <param name="Ctrl"></param>
        /// <param name="CancelDefault"></param>
        static void insFieldBtn_Click(Core.CommandBarButton Ctrl, ref bool CancelDefault)
        {
            Helper.Trace("static void insFieldBtn_Click(Core.CommandBarButton Ctrl, ref bool CancelDefault)", false);
            Word.Document doc = application.ActiveDocument;


            if (doc == null)
            {
                return;
            }
            // Why do we actually look whether the doc is readonly or not? 
            // read only files can have attachdoc with them?? right? 
            if (doc.ReadOnly)
            {
                MessageBox.Show(global::CRMScenarios.Resources.DocReadOnlyMessage);
                return;
            }

            try
            {
                if (doc.Email.CurrentEmailAuthor != null)
                {
                    int docHashCode = doc.GetHashCode();

                    if (MappingFromDocToForm.ContainsKey(docHashCode) == false)
                    {
                        ActionsPane SolutionForOutlook = new ActionsPane();
                        SolutionForOutlook.Show();
                        SolutionForOutlook.Store(docHashCode, ref MappingFromDocToForm);
                        SolutionForOutlook.InitializeControls(application, doc);
                        MappingFromDocToForm.Add(docHashCode, SolutionForOutlook);
                    }
                    return;
                }
            }
            catch (Exception )
            {
                Helper.Trace("Not OTL", false);
                //Helper.Trace(e.Message + "\n" + e.StackTrace);
            }
            
            if (OfficeApi.GetCustomProperty(doc, "CRMVSTO") != null)
            {
                // Show actionsPane for CRM.
                application.TaskPanes[Microsoft.Office.Interop.Word.WdTaskPanes.wdTaskPaneDocumentActions].Visible = true;
                return;
            }
            ArrangeAndDoAddCustomizationWord(doc, 1); // 2 is for attach doc
        }

        /// <summary>
        /// Attaching the assembly available at path to the document and make sure that the 
        /// scenario needed will come once the documetn is opened.
        /// </summary>
        /// <param name="doc"></param>
        /// <param name="pathToAssembly"></param>
        /// <param name="scenario"></param>
        static void closeAttachReOpen(Word.Document doc, string pathToAssembly, int scenario)
        {
            object o = System.Reflection.Missing.Value;
            string partialPath = null;
            string docName = null;
            string fullPath = null;
            object objfullPath = null;
            bool cont = true;

            Helper.Trace("static int closeAttachReOpen(Word.Document doc, string pathToAssembly, string manifest, string version, int scenario)", false);

            if ((doc == null) || (pathToAssembly == null))
            {
                return;
            }

            if (!OfficeApi.IsDocumentSaved(doc))
            {
                DialogResult okayCancel = Util.AskQues(global::CRMScenarios.Resources.AttachOrNotQuestionMsg);

                if (okayCancel == DialogResult.Cancel)
                {
                    return;
                }
            }
            // TODO - why TRY CATCH here?
            try
            {
                /* 
                 * Add custom properties to the document which makes sure that the assembly is added.
                 * and on failure clean the document.
                 * 
                 * if the assembly location is more than 255 chars, split it and add in two custom properties.
                 */

                OfficeApi.SetCustomProperty(doc, Convert.ToString(scenario, 10), "CRMVSTO");
                OfficeApi.SetCustomProperty(doc, "*", "_AssemblyName");
                if (pathToAssembly.Length < 255)
                {
                    OfficeApi.SetCustomProperty(doc, pathToAssembly, "_AssemblyLocation");
                }
                else 
                {
                    string strPathFirst = pathToAssembly.Substring(0, 255);
                    string strPathSecond = pathToAssembly.Substring(255, pathToAssembly.Length);
                    OfficeApi.SetCustomProperty(doc, strPathFirst, "_AssemblyLocation0");
                    OfficeApi.SetCustomProperty(doc, strPathSecond, "_AssemblyLocation1");
                }
                cont = true;
            }
            catch
            {
                // UNDO THE ABOVE AND RETURN
                cont = false;
                OfficeApi.RemoveCustomProperty(doc, "_AssemblyLocation");
                OfficeApi.RemoveCustomProperty(doc, "_AssemblyLocation0");
                OfficeApi.RemoveCustomProperty(doc, "_AssemblyLocation1");
                OfficeApi.RemoveCustomProperty(doc, "CRMVSTO");
                OfficeApi.RemoveCustomProperty(doc, "_AssemblyName");
            }
  
            try
            {
                object sC = Word.WdSaveOptions.wdSaveChanges;
                try
                {
                    doc.Save();
                }
                catch
                {
                    Util.ShowMsg(global::CRMScenarios.Resources.ErrorWhileSavingFile);
                    OfficeApi.RemoveCustomProperty(doc, "CRMVSTO");
                    return;
                }

                if (!cont)
                {
                    Util.ShowMsg(global::CRMScenarios.Resources.VSTOAttachErrorMsg);
                    return;
                }
                partialPath = doc.Path;
                docName = doc.Name;
                fullPath = partialPath + "\\" + docName;
                objfullPath = (object)fullPath;
                ((Word._Document)doc).Close(ref sC, ref o, ref o);
            }
            catch (Exception docClose)
            {
                Helper.Trace("Document save close\n" + docClose.StackTrace, false);
                Util.ShowError(global::CRMScenarios.Resources.ErrorWhileClosing);
                return;
            }

            /* 
             * now all the properties are added and the document is closed. Let's open the document
             * and things will work fine
             */

            try
            {
                doc = application.Documents.Open(ref objfullPath, ref o, ref o, ref o, ref o,
                                 ref o, ref o, ref o, ref o, ref o, ref o,
                                 ref o, ref o, ref o, ref o, ref o);
            }
            catch(Exception docOpen)
            {
                Helper.Trace("Doc open\n" + docOpen.StackTrace, false);
                Util.ShowError(global::CRMScenarios.Resources.DocOpenErrMsg);
            }
        }

        /// <summary>
        /// Take actions on opening of a document. 
        /// If outlook mail the acitons are different from an ordinary word document.
        /// </summary>
        /// <param name="Doc"></param>
        static void DocumentOpen(Microsoft.Office.Interop.Word.Document Doc)
        {
            Helper.Trace("static void DocumentOpen(Microsoft.Office.Interop.Word.Document Doc)", false);
            toolBar.Enabled = true;
            toolBar.Visible = true;
            DoActiveOrHideAccordingly(Doc);
            if (Helper.IsTempFile(Doc.Path))
            {
                Doc.Application.TaskPanes[Microsoft.Office.Interop.Word.WdTaskPanes.wdTaskPaneDocumentActions].Visible = false;
            }
        }

        /// <summary>
        /// Do a clean up action before exiting after all the work.
        /// It is always a good practice to unsubscribe the events, release the 
        /// resources, delete the buttons etc.
        /// </summary>
        public static void cleanUpApplication()
        {
            Helper.Trace("public static void cleanUpApplication()", false);
            object missing = System.Reflection.Missing.Value;
            if (toolBar != null)
            {
                if (insFieldBtn != null)
                {
                    insFieldBtn.Click -= new Core._CommandBarButtonEvents_ClickEventHandler(insFieldBtn_Click);
                    //insFieldBtn.Delete(missing);
                }

                toolBar.Delete();
            }
            try
            {
                appClass.ApplicationEvents2_Event_NewDocument -= new Word.ApplicationEvents2_NewDocumentEventHandler(DocumentOpen);
                appClass.ApplicationEvents2_Event_DocumentOpen -= new Word.ApplicationEvents2_DocumentOpenEventHandler(DocumentOpen);
                application.DocumentOpen -= new Word.ApplicationEvents4_DocumentOpenEventHandler(DocumentOpen);
                application.DocumentChange -= new Word.ApplicationEvents4_DocumentChangeEventHandler(DocumentChange);
                application.DocumentBeforeClose -= new Word.ApplicationEvents4_DocumentBeforeCloseEventHandler(DocumentClose);
                application.WindowDeactivate -= new Word.ApplicationEvents4_WindowDeactivateEventHandler(application_WindowDeactivate);
                application.WindowActivate -= new Word.ApplicationEvents4_WindowActivateEventHandler(application_WindowActivate);
            }
            catch(Exception exp)
            {
                Helper.Trace("Event UnHandling\n" + exp.StackTrace, false);
            }
        }
    }   
}
